﻿using Devil;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace DynamicBone
{
    public class DynamicBoneAvatar : ScriptableObject
    {
        [System.Serializable]
        public class Bone
        {
            public string name;
            public int parentIndex;
            public Vector3 initPosition;
            public Quaternion initRotation;
        }

        [SerializeField]
        float m_BoneLength;

        [SerializeField]
        List<Bone> m_Bones;

#if UNITY_EDITOR
        public static void UpdateAvatarAsset(DynamicBoneAvatar avatar, DynamicBoneRoot system)
        {
            var lst = CachedList<CachedParticle>.GetList();// new List<CachedParticle>();
            EDynamicBoneFlags flags = 0;
            InitParticles(lst, system, ref flags, ref avatar.m_BoneLength);
            if (avatar.m_Bones == null)
                avatar.m_Bones = new List<Bone>(lst.Count);
            else
                avatar.m_Bones.Clear();
            for (int i = 0; i < lst.Count; i++)
            {
                var particle = lst[i];
                var bone = new Bone();
                bone.name = particle.transform.name;
                bone.initPosition = particle.data.initLocalPosition;
                bone.initRotation = particle.data.initLocalRotation;
                bone.parentIndex = particle.data.parentIndex;
                avatar.m_Bones.Add(bone);
            }
            UnityEditor.EditorUtility.SetDirty(avatar);
            CachedList<CachedParticle>.Release(lst);
        }
#endif

        static void InitParticles(IList<CachedParticle> particles, DynamicBoneRoot system, ref EDynamicBoneFlags flags, ref float totalBoneLength)
        {
            particles.Clear();
            //var rootnum = 0;
            if (system.m_Root == null || system.m_Root == system.transform)
            {
                var len = system.transform.childCount;
                for (int i = 0; i < len; i++)
                {
                    var trans = system.transform.GetChild(i);
                    AppendParticles(particles, system, trans, -1, 0, ref totalBoneLength/*, ref rootnum*/);
                }
            }
            else
            {
                AppendParticles(particles, system, system.m_Root, -1, 0, ref totalBoneLength/*, ref rootnum*/);
            }
            
        }

        public static void SetupParticles(IList<CachedParticle> particles, DynamicBoneRoot system, ref EDynamicBoneFlags flags, ref float totalBoneLength)
        {
            var avatar = system.m_Avatar;
            if (avatar != null)
            {
                totalBoneLength = avatar.m_BoneLength;
                particles.Clear();
                int skips = 0;
                var size = avatar.m_Bones == null ? 0 : avatar.m_Bones.Count;
                var root = system.m_Root == null ? system.transform : system.m_Root;
                if (root == system.transform)
                {
                    for (int i = 0; i < size; i++)
                    {
                        var bone = avatar.m_Bones[i];
                        var parentindex = bone.parentIndex - skips;
                        var parent = parentindex < 0 ? null : particles[parentindex];
                        Transform trans;
                        if (parent == null)
                            trans = root.Find(bone.name);
                        else
                            trans = parent.transform.Find(bone.name);
                        if (trans == null)
                        {
                            skips++;
                            continue;
                        }
                        AppendParticles(particles, system, trans, bone, parentindex, parent == null ? 0 : parent.boneLength);
                    }
                }
                else if (size > 0)
                {
                    AppendParticles(particles, system, root, avatar.m_Bones[0], -1, 0);
                    for (int i = 1; i < size; i++)
                    {
                        var bone = avatar.m_Bones[i];
                        var parentindex = bone.parentIndex - skips;
                        var parent = parentindex < 0 ? null : particles[parentindex];
                        Transform trans;
                        if (parent == null)
                            trans = root.Find(bone.name);
                        else
                            trans = parent.transform.Find(bone.name);
                        if (trans == null)
                        {
                            skips++;
                            continue;
                        }
                        AppendParticles(particles, system, trans, bone, parentindex, parent == null ? 0 : parent.boneLength);
                    }
                }
            }
            else
            {
                InitParticles(particles, system, ref flags, ref totalBoneLength);
            }
            for (int i = 0; i < particles.Count; i++)
            {
                particles[i].UpdateParameters(system, totalBoneLength);
            }
        }

        static void AppendParticles(IList<CachedParticle> particles, DynamicBoneRoot system, Transform b, Bone bone, int parentIndex, float boneLength)
        {
            CachedParticle p = new CachedParticle(b);
            DynamicParticle pdata = new DynamicParticle();
            pdata.parentIndex = (short)parentIndex;
            //if (parentIndex == -1)
            //    rootnum++;
            pdata.position = pdata.prevPosition = b.position;

            pdata.initLocalRotation = bone.initRotation;
            pdata.initLocalPosition = bone.initPosition;

            if (parentIndex >= 0)
            {
                boneLength += (particles[parentIndex].transform.position - pdata.position).magnitude;
                p.boneLength = boneLength;
            }

            p.data = pdata;
            particles.Add(p);
        }

        static void AppendParticles(IList<CachedParticle> particles, 
            DynamicBoneRoot system, Transform b, int parentIndex, float boneLength, ref float maxBoneLength/*, ref int rootnum*/)
        {
            if (!b.gameObject.activeSelf || (system.m_Exclusions != null && system.m_Exclusions.Contains(b)))
                return;
            CachedParticle p = new CachedParticle(b);
            DynamicParticle pdata = new DynamicParticle();
            pdata.parentIndex = (short)parentIndex;
            //if (parentIndex == -1)
            //    rootnum++;
            pdata.position = pdata.prevPosition = b.position;

            var rot = system.transform.rotation;
            rot.x = -rot.x;
            rot.y = -rot.y;
            rot.z = -rot.z;
            pdata.initLocalRotation = rot * b.rotation;
            pdata.initLocalPosition = system.transform.worldToLocalMatrix.MultiplyPoint(b.position);

            if (parentIndex >= 0)
            {
                boneLength += (particles[parentIndex].transform.position - pdata.position).magnitude;
                p.boneLength = boneLength;
                maxBoneLength = Mathf.Max(maxBoneLength, boneLength);
            }

            int index = particles.Count;
            p.data = pdata;
            particles.Add(p);

            if (b != null)
            {
                for (int i = 0; i < b.childCount; ++i)
                {
                    var child = b.GetChild(i);
                    AppendParticles(particles, system, child, index, boneLength, ref maxBoneLength/*, ref rootnum*/);
                }
            }
        }


        public class CachedParticle
        {
            public Transform transform;
            public DynamicParticle data;
            public float boneLength;

            public CachedParticle() { }

            public CachedParticle(Transform trans)
            {
                transform = trans;
            }

            public void UpdateParameters(DynamicBoneRoot sys, float boneTotalLength)
            {
                data.damping = sys.m_Damping;
                data.elasticity = sys.m_Elasticity;
                data.stiffness = sys.m_Stiffness;
                data.inert = sys.m_Inert;
                data.friction = sys.m_Friction;
                data.radius = sys.m_Radius;

                if (boneTotalLength > 0)
                {
                    float a = boneLength / boneTotalLength;
                    if (sys.m_DampingDistrib != null && sys.m_DampingDistrib.length > 0)
                        data.damping *= sys.m_DampingDistrib.Evaluate(a);
                    if (sys.m_ElasticityDistrib != null && sys.m_ElasticityDistrib.length > 0)
                        data.elasticity *= sys.m_ElasticityDistrib.Evaluate(a);
                    if (sys.m_StiffnessDistrib != null && sys.m_StiffnessDistrib.length > 0)
                        data.stiffness *= sys.m_StiffnessDistrib.Evaluate(a);
                    if (sys.m_InertDistrib != null && sys.m_InertDistrib.length > 0)
                        data.inert *= sys.m_InertDistrib.Evaluate(a);
                    if (sys.m_FrictionDistrib != null && sys.m_FrictionDistrib.length > 0)
                        data.friction *= sys.m_FrictionDistrib.Evaluate(a);
                    if (sys.m_RadiusDistrib != null && sys.m_RadiusDistrib.length > 0)
                        data.radius *= sys.m_RadiusDistrib.Evaluate(a);
                }

                data.damping = Mathf.Clamp01(data.damping);
                data.elasticity = Mathf.Clamp01(data.elasticity);
                data.stiffness = Mathf.Clamp01(data.stiffness);
                data.inert = Mathf.Clamp01(data.inert);
                data.friction = Mathf.Clamp01(data.friction);
                data.radius = Mathf.Max(data.radius, 0);
            }


        }

    }


}